// add put and delete to jquery to simplify api
// http://homework.nwsnet.de/releases/9132/

var _URL = "crud/Student/";
function _ajax_request(url, data, callback, type, method) {
    if (jQuery.isFunction(data)) {
        callback = data;
        data = {};
    }
    return jQuery.ajax({
        type: method,
        url: url,
        data: data,
        success: callback,
        dataType: type
        });
}

jQuery.extend({
    put: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, 'PUT');
    },
    delete_: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, 'DELETE');
    }
});


// single model
function Student(data) {
    var self = this;
    
    // observable properties - they fire events when changed to notify UI
    self.name = ko.observable(data.name);
    self.firstName = ko.observable(data.firstName);
    self.id = ko.observable(data.id);
    // derived value
    self.fullName = ko.computed(function() {
        return this.firstName() + " " + self.name();
    }, self);
    
    // by default, knockout does little to map objects to 
    // REST full services
    // we add crud methods here - thsi requires to keep track of persistency state:
    // whether the object is new, and whether the object has unsaved changes
    self.isDirty = ko.observable(false);
    // the ids are assigned when the objects are saved on the server
    self.isNew = ko.computed(function() {
       return !self.id();
    }, self);
    
    // keep track of dirty state 
    self.name.subscribe(function() { 
        self.isDirty(true); 
    });
    self.firstName.subscribe(function() {
        self.isDirty(true); 
    });
}

// collection model
function StudentListViewModel() {
    // Data
    var self = this;
    self.students = ko.observableArray([]);
    // factories for new values
    self.newStudentName = ko.observable();
    self.newStudentFirstName = ko.observable();
    
    // use jquery ajax to fetch records
    $.getJSON(_URL, function(allData) {
        // use jquery map to create instances from json returned
        var students = $.map(allData, function(item) { return new Student(item) });
        self.students(students);
    });

    // CRUD methods
    self.add = function() {
        self.students.push(new Student({ name: self.newStudentName(), firstName: self.newStudentFirstName() }));
        // reset 
        self.newStudentName("");
        self.newStudentFirstName("");
    };
    self.remove = function(student) {
        if (student.isNew()) {
            // this student is not persistent - only delete it from the local list
            self.students.remove(student);
        }
        else {
            console.log("deleting existing student");
            $.delete_(
                _URL+student.id(),
                function(s) {
                    console.log("object deleted");
                    self.students.remove(student);
                }
            );
        }
    };   
    // refresh values from id
    self.refresh = function(student) {
        console.log("refreshing existing student " + student.id());
        $.get(
            _URL+student.id(),
            function(s) {
                // here we manually copy properties - with the knockout mapping plugin thsi could be automated
                for (var prop in s) {
                   console.log("updating " + prop + " -> " + s[prop]); 
                   student.firstName(s.firstName);
                   student.name(s.name);
                }
                console.log("object refreshed");
            }
        );
    }; 
    self.save = function(student) { 
        // must clone object to remove derived attributes (status attributes like isNew 
        // should not be saved ! )
        // ko.toJSON will decode onservables correctly
        // remark: this could be handled more elegantly with the knockout mapping plugin
        var clone = $.parseJSON(ko.toJSON(student));
        delete clone.isDirty;
        delete clone.isNew;
        delete clone.fullName;
        
        if (student.isNew()) {
            console.log("saving new student");
            delete clone.id;
            var r = ko.toJSON(clone);
            $.post(
                _URL,
                r,
                function(s) {
                    console.log("saved object to db, assigned id is " + s.id);
                    // the server returns the object with an id, this will mark the object as persistent
                    student.id(s.id);
                },
                "json"
            );
        }
        else if (student.isDirty()) {
            console.log("updating existing object");
            var r = ko.toJSON(clone);
            $.put(
                _URL+student.id(),
                r,
                function(s) {
                    console.log("object updated");
                    // the object has been saved now!
                    student.isDirty(false);
                }
            );
        }
        else {
            console.log("nothing to to - object does not need saving");
        }
    };
    
    // bulk CRUD methods: implemented by calling the CRUD methods for each object
    // this is easy to implement but not very efficient, it would be better to 
    // submit all objects as arrays to the server
    self.saveAll = function() {
        for (var i in self.students()) {
            self.save(self.students()[i]);
        }
    };
    self.removeAll = function() {
        for (var i in self.students()) {
            self.remove(self.students()[i]);
        }
    };
    self.refreshAll = function() {
        for (var i in self.students()) {
            self.refresh(self.students()[i]);
        }
    };
}
